/*
 * Copyright 2020-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.sagacity.framework.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ReflectUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sagacity.framework.annotation.TableId;
import com.sagacity.framework.annotation.TableLogic;
import com.sagacity.framework.api.model.request.Criterion;
import com.sagacity.framework.api.model.request.GenericQueryBO;
import com.sagacity.framework.api.model.request.OrderBy;
import com.sagacity.framework.api.model.response.PageResult;
import com.sagacity.framework.constants.Common;
import com.sagacity.framework.exception.AppException;
import com.sagacity.framework.jdbc.BaseMapper;
import com.sagacity.framework.service.constant.MethodType;
import com.sagacity.framework.util.*;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import static com.sagacity.framework.api.constant.ResponseCode.ARGUMENT_ILLEGAL;

/**
 * 基础服务公共方法
 *
 * @author xingyun
 * @date 2020-07-04 12:58
 */
@Slf4j
public abstract class BaseService<M extends BaseMapper<T>, T> implements Service<T> {

    @Autowired
    private M mapper;


    private static final Map<String, String> CACHE_PK_NAME = new ConcurrentHashMap<>();
    private static final Map<String, String> CACHE_LOGIC_NAME = new ConcurrentHashMap<>();


    /**
     * 插入一条记录
     *
     * @param entity 实体对象
     */
    public Integer insert(T entity) {
        setDefault(entity, MethodType.INSERT);
        return mapper.insert(entity);
    }


    /**
     * 根据 ID 删除
     *
     * @param entity 主键ID
     */
    public Integer deleteById(T entity) {
        checkId(entity);
        log.info("物理删除数据,入参：{}",JsonUtil.toJson(entity));
        // 物理删除
        return mapper.deleteById(entity);
    }


    /**
     * 根据 ID 逻辑删除
     *
     * @param entity 主键ID
     */
    public Integer disableById(T entity) {
        checkId(entity);
        setDefault(entity, MethodType.LOGIC_DELETE);
        log.info("逻辑删除数据,入参：{}",JsonUtil.toJson(entity));
        // 逻辑删除
        return mapper.disableById(entity);
    }

    private void checkId(T entity) {
        Object id = ReflectUtil.getFieldValue(entity, primaryKey(entity));
        if(id == null){
            throw new AppException(ARGUMENT_ILLEGAL,"主键不能为空");
        }
    }

    private String primaryKey(T entity){
        Class<?> clazz = ClassUtils.getUserClass(entity.getClass());
        return CACHE_PK_NAME.computeIfAbsent(clazz.getCanonicalName(),p->{
            // 字段
            List<Field> allFields = ReflectionKit.getFieldList(clazz);
            for (Field field : allFields) {
                TableId tableId = field.getAnnotation(TableId.class);
                if(tableId != null){
                    return field.getName();
                }
            }
            return "id";
        });
    }

    /**
     * 根据 ID 修改
     *
     * @param entity 实体对象
     */
    public Integer updateById(T entity) {
        checkId(entity);
        setDefault(entity, MethodType.UPDATE);
        return mapper.updateById(entity);
    }

    /**
     * 根据 ID 查询
     *
     * @param id 主键ID
     */
    public T selectById(Serializable id) {
        return mapper.selectById(id);
    }

    /**
     * 查询（根据ID 批量查询）
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    public List<T> selectBatchIds(List<Serializable> idList) {
        return mapper.selectBatchIds(idList);
    }

    /**
     * 根据 entity 条件，查询一条记录
     *
     * @param entity 实体对象（可以为 null）
     */
    public T selectOne(T entity) {
        return mapper.selectOne(entity);
    }

    /**
     * 根据 entity 条件，查询总记录数
     *
     * @param entity 实体对象（可以为 null）
     */
    public Integer selectCount(T entity) {
        return mapper.selectCount(entity);
    }

    /**
     * 根据 entity 条件，查询全部记录
     *
     * @param entity 实体对象（可以为 null）
     */
    public List<T> selectList(T entity) {
        return mapper.selectList(entity);
    }

    /**
     * 根据 entity 或 generalConditions 条件，分页查询数据
     *
     * @param queryBO 分页查询条件
     */
    public PageResult<T> search(GenericQueryBO<T> queryBO) {
        // 处理默认值
        handleQueryBO(queryBO);
        // 分页参数
        Page<Object> page = PageHelper.startPage(queryBO.getPageNum(),
                queryBO.getPageSize());
        // 分页合理化
        page.setReasonable(true);
        // 排序字段
        page.setOrderBy(handleOrderBy(queryBO));
        List<T> tList = mapper.search(queryBO);
        return PageResult.of(tList);
    }

    private String handleOrderBy(GenericQueryBO<T> queryBO) {
        if(CollectionUtil.isEmpty(queryBO.getOrderBys())){
            return null;
        }
        return queryBO.getOrderBys().stream().map(orderBy -> orderBy.getFieldName() + " " + (orderBy.isAsc()?"ASC": "DESC")).collect(Collectors.joining(","));
    }

    /**
     * <p>
     *     处理查询条件对象
     * </p>
     * @param queryBO 查询对象
     */
    private void handleQueryBO(GenericQueryBO<T> queryBO){
        queryBO.setPageNum(queryBO.getPageNum() == 0 ? 1 : queryBO.getPageNum());
        queryBO.setPageSize(queryBO.getPageSize() == 0 ? 15 : queryBO.getPageSize());
        // 复杂查询条件校验
        if(queryBO.getConditions() != null && !CollectionUtils.isEmpty(queryBO.getConditions().getCriteria())){
            List<Criterion> criteria = queryBO.getConditions().getCriteria();
            for (int i = 0; i < criteria.size(); i++) {
                Criterion criterion = criteria.get(i);
                criterion.handleCriterion();
                // 将最后一个拼接条件的connect值去掉
                if(i == criteria.size() - 1){
                    criterion.setConnect(null);
                }
            }

            // 圆括号组合合理性校验
            long prefixCount = criteria.parallelStream().filter(criterion -> StringUtil.isNoneBlank(criterion.getPrefix())).count();
            long suffixCount = criteria.parallelStream().filter(criterion -> StringUtil.isNoneBlank(criterion.getSuffix())).count();
            if(prefixCount != suffixCount){
                log.warn("查询条件缺乏合理性，请检查,criteria:{}",JsonUtil.toJson(criteria));
                throw new AppException(ARGUMENT_ILLEGAL,"查询条件缺乏合理性，请检查");
            }
        }
        // 校验包含的字段
        if(CollectionUtil.isNotEmpty(queryBO.getIncludes())){
            List<String> newIncludes = new ArrayList<>();
            for (String include : queryBO.getIncludes()) {
                if(!PatternUtil.FIELD_NAME.matcher(include).matches()){
                    throw new AppException(ARGUMENT_ILLEGAL,"仅需查询字段入参非法");
                }
                // 驼峰转下划线
                newIncludes.add(StringUtil.toUnderlineCase(include));
            }
            queryBO.setIncludes(newIncludes);
        }
        // 校验排序字段
        if(CollectionUtil.isNotEmpty(queryBO.getOrderBys())){
            for (OrderBy orderBy : queryBO.getOrderBys()) {
                String fieldName = orderBy.getFieldName();
                if(!PatternUtil.FIELD_NAME.matcher(fieldName).matches()){
                    throw new AppException(ARGUMENT_ILLEGAL,"排序字段入参非法");
                }
                // 驼峰转下划线
                orderBy.setFieldName(StringUtil.toUnderlineCase(fieldName));
            }
        }
    }


    /**
     * 根据 entity 或 generalConditions 条件，分页查询数据
     *
     * @param queryBO 分页查询条件
     */
    public void export(GenericQueryBO<T> queryBO) {
        PageResult<T> search = search(queryBO);
        List<T> list = search.getList();
        ExcelUtils.exportExcel(list);
    }

    /**
     * 设置默认值
     *
     * @param entity 实体对象
     */
    private void setDefault(T entity, MethodType methodType) {
        try {
            if (MethodType.INSERT.equals(methodType)) {
                // 主键ID
                ReflectUtil.setFieldValue(entity, primaryKey(entity), IdClient.nextId());
                // 创建时间、创建人
                ReflectUtil.setFieldValue(entity, "createTime", DateUtil.date());
                ReflectUtil.setFieldValue(entity, "createBy", RequestUtil.getRequestInfo().getName());
            } else if (MethodType.UPDATE.equals(methodType) || MethodType.LOGIC_DELETE.equals(methodType)) {
                // 修改时间、修改人
                ReflectUtil.setFieldValue(entity, "modifyTime", DateUtil.date());
                ReflectUtil.setFieldValue(entity, "modifyBy", RequestUtil.getRequestInfo().getName());
            }
            // 逻辑删除
            String logicProperty = logicProperty(entity);
            if(StringUtil.isNoneBlank(logicProperty)){
                if (MethodType.LOGIC_DELETE.equals(methodType)) {
                    if (StringUtil.isBlank(logicProperty)){
                        throw new AppException("没有逻辑删除字段，不支持操作");
                    }
                    // 有传入，则以传入为准，没有传入，则将逻辑字段设置为'0'
                    Object fieldValue = ReflectUtil.getFieldValue(entity, logicProperty);
                    if(fieldValue == null){
                        ReflectUtil.setFieldValue(entity, logicProperty, Common.NO_NUM);
                    }
                } else {
                    ReflectUtil.setFieldValue(entity, logicProperty, Common.YES_NUM);
                }
            }
            // traceId
            ReflectUtil.setFieldValue(entity, "traceId", MDC.get("traceId"));
        } catch (Exception e) {
            log.warn("{}设置默认值异常，非规范表", methodType, e);
        }
    }

    /**
     * 找到逻辑删除字段
     */
    private String logicProperty(T entity){
        Class<?> clazz = ClassUtils.getUserClass(entity.getClass());
        return CACHE_LOGIC_NAME.computeIfAbsent(clazz.getCanonicalName(),p->{
            // 字段
            List<Field> allFields = ReflectionKit.getFieldList(clazz);
            for (Field field : allFields) {
                TableLogic tableLogic = field.getAnnotation(TableLogic.class);
                if(tableLogic != null){
                    return field.getName();
                }
            }
            return null;
        });
    }
}
